package com.example.demo2.service.impl;

import com.example.demo2.dao.TestMapper;
import com.example.demo2.service.ICheckService;
import com.example.demo2.service.ITestCase3Service;
import com.example.demo2.service.ITestCase4Service;
import com.example.demo2.service.ITestService;
import com.example.demo2.utils.HttpUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import static com.example.demo2.utils.HttpUtils.check;

@Slf4j
@Service
public class TestServiceImpl implements ITestService {
    @Autowired
    private TestMapper testMapper;

    @Autowired
    private ITestCase3Service testCase3Service;
    @Autowired
    private ITestCase4Service testCase4Service;
    @Autowired
    private ICheckService checkService;

    @Autowired
    private DataSourceTransactionManager txManager;

    @Resource(name = "testExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;


    /**
     * 不用 @Transactional
     * 此时每一句SQL自动commit
     */
    @Override
    public void case1() {
        case1Do();
    }

    private void case1Do() {
        log.info("插入一条数据");
        // 插入一条数据
        testMapper.insert("case1");
        // 查看当前数据库的数据
        check();
        log.info("删除一条数据");
        // 删除一条数据
        testMapper.delete("case1");
        // 查看当前数据库的数据
        check();
        log.info("插入之后数据可以直接在其他事物中查到");
    }

    @Override
    @Transactional
    public void case2() {
        case2Do();
        log.info("case2Do 方法没有用 @Transactional，但是事务依然生效");
    }

    private void case2Do() {
        log.info("------------------------------------------------");
        log.info("插入一条数据");
        // 插入一条数据
        testMapper.insert("case2");
        log.info("------------------------------------------------");
        // 查看当前数据库的数据
        check();
        log.info("插入之后数据不可以可以直接在其他事物中查到");
        log.info("------------------------------------------------");
        checkService.check();
        log.info("在自己的事务中可以查到");
    }

    @Override
    public void case3() {
        log.info("------------------------------------------------");
        case3Do();
        log.info("case3Do 方法用了 @Transactional，但是事务不生效，因为 case3Do 没有用 Spring 进行管理");
        log.info("case3Do 方法 和 case1Do 方法 处理的过程是一模一样的");
        log.info("------------------------------------------------");
        testCase3Service.case3Do();
        log.info("case3Do 方法 在受到 Spring 的管理后， 事务是可以生效的");
        log.info("------------------------------------------------");
        testCase3Service.case3Do2();
        log.info("case3Do2 方法 的 @Transactional 加在接口定义里也可以");
        log.info("------------------------------------------------");
    }

    @Transactional
    private void case3Do() {
        log.info("插入一条数据");
        // 插入一条数据
        testMapper.insert("case3");
        // 查看当前数据库的数据
        check();
        log.info("删除一条数据");
        // 删除一条数据
        testMapper.delete("case3");
        // 查看当前数据库的数据
        check();
        log.info("插入之后数据可以直接在其他事物中查到");
    }

    @Override
    public void case4() {
        log.info("------------------------------------------------");
        // 	Propagation propagation() default Propagation.REQUIRED;
        // 默认的传播级别是 REQUIRED
        log.info("传播级别为 REQUIRES_NEW 时，将会新建一个事务，事务与事务之间相互隔离");
        testCase4Service.requiresNew();

        log.info("------------------------------------------------");
        testCase4Service.withTransactionalSupports();
        log.info("有事务时，使用事务，在其他事务中查不到 case4.2");
        testMapper.delete("case4.2");
        log.info("------------------------------------------------");
        testCase4Service.withoutTransactionalSupports();
        log.info("没有事务时，改动直接提交，在其他事务中可以查到 case4.2");
        testMapper.delete("case4.2");

        log.info("------------------------------------------------");
        // MANDATORY 必需有事务
        try {
            testCase4Service.mandatory();
        } catch (IllegalTransactionStateException e) {
            log.info("没有事务时，直接捕获到异常");
        }

        log.info("------------------------------------------------");
        // NOT_SUPPORTED
        testCase4Service.notSupported();
        log.info("非事务操作，如果当前存在事务，也不进行事务操作");

        log.info("------------------------------------------------");
        // NEVER 必需没有事务
        // 不支持事务，如果当前存在事务，抛出异常
        try {
            testCase4Service.never();
        } catch (IllegalTransactionStateException e) {
            log.info("有事务时，直接捕获到异常");
        }

        log.info("------------------------------------------------");
        // NESTED       嵌套事务，与 REQUIRES_NEW 的区别是，可以读取到被嵌套的事务；嵌套事务的提交时机为被嵌套的事务提交时
        // 我自己试下来和 REQUIRED 一样
        testCase4Service.nested();
        testCase4Service.nestedWithNested();
    }

    @SneakyThrows
    @Override
    public void case0(String flag) {
        // 手动拿到了事务管理器，但是没有进行任何操作
        // 开启事务
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        if ("3".equals(flag)) {
            log.info("请求 case0a flag: {}", flag);
            HttpUtils.get("http://127.0.0.1:8888/test/case0a");
            log.info("请求 case0a done flag: {}", flag);
            // 不去 getTransaction 也不会有影响
            log.info("情况0 done flag: {}", flag);
        } else {
            log.info("请求 case0a flag: {}", flag);
            TransactionStatus status = txManager.getTransaction(def);
            HttpUtils.get("http://127.0.0.1:8888/test/case0a");
            log.info("请求 case0a done flag: {}", flag);

            // rollback或者commit，数据就可以正常入库
            if ("1".equals(flag)) {
                txManager.rollback(status);
            } else if ("2".equals(flag)) {
                txManager.commit(status);
            }
            // 如果不rollback，也不commit，会影响其他事务的提交
            log.info("情况0 done flag: {}", flag);
        }


    }

    @SneakyThrows
    @Transactional
    @Override
    public void case0a() {
        log.info("情况0a");
        testMapper.insert("case0");
        // 先不提交事务，模拟在做其他的事情，让 txManager 对事务进行 rollback
//        Thread.sleep(2000);
        log.info("情况0a done");
    }

    @SneakyThrows
    @Override
    public void case0b() {
        // commit
        case0WithFlag("2");
        log.info("commit...ok!");
        // rollback
        case0WithFlag("1");
        log.info("rollback...ok!");
        // 既不commit也不rollback，也不占用 txManager.getTransaction(def)
        case0WithFlag("3");
        log.info("do nothing...ok!");
        // 既不commit也不rollback，只是占用了 txManager.getTransaction(def)
        case0WithFlag("0");
        log.info("txManager...bad!");
    }

    private void case0WithFlag(String flag) throws InterruptedException, ExecutionException {
        testMapper.delete("case0");
        List<Future<Integer>> futureList = new ArrayList<>();
        for (int i = 0; i < 500; i++) {
            int finalI = i;
            Future<Integer> future = threadPoolTaskExecutor.submit(() -> {
                try {
                    log.info("new Thread");
                    HttpUtils.get("http://127.0.0.1:8888/test/case0?flag=" + flag);
                    log.info("Thread done");
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
                return finalI;
            });
            futureList.add(future);
        }
        for (Future<Integer> future : futureList) {
            future.get();
        }
        check();
    }

}
